using System.Text;
using System.Security.Claims;
using WebapiTemplate.Domain.Entity;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Extensions.Configuration;
using WebapiTemplate.Application.RequestDto;
using WebapiTemplate.Application.ResponesDto;
using WebapiTemplate.Application.Common.Interface;
using WebapiTemplate.Application.JwtConfigurationModel;

namespace WebapiTemplate.Infrastructure.Identity
{
    /// <summary>
    /// 操作token的IIdentityService接口(手动建)实现类
    /// </summary>
    public class IdentityService : IIdentityService
    {
        //定义用户表实例字段，用于验证用户是否存在以及生产时的token加密信息
        private readonly IRepository<User> _appUserRespository;

        //定义存放用户和对应token信息表实例字段，用于根据RefreshToken字段判断是否刷新token
        private readonly IRepository<AppIdentityUser> _appIdentityUserRespository;

        //定义项目的Configuration实例字段，用于获取appsettings.json文件的生产token原材料
        private readonly IConfiguration _configuration;

        //依赖注入得到字段的真正实例
        public IdentityService(
            IConfiguration configuration,
            IRepository<User> appUserRespository,
            IRepository<AppIdentityUser> appIdentityUserRespository
        )
        {
            _configuration = configuration;
            _appUserRespository = appUserRespository;
            _appIdentityUserRespository = appIdentityUserRespository;
        }

        //实现接口的生成token方法
        public async Task<AppTokenDto> GenerateToken(User appUser)
        {
            //获取生产token的配置
            var jwtSetting = _configuration.GetSection("JwtSetting").Get<JwtParameterModel>();

            //秘钥
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.Secret));
            //证书
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            //生成token
            var token = new JwtSecurityTokenHandler().WriteToken(
                new JwtSecurityToken(
                    jwtSetting.Issuer,
                    jwtSetting.Audience,
                    new List<Claim>
                    {
                        new Claim("UserId", appUser.Id.ToString()),
                        new Claim("Username", appUser!.Username),
                    },
                    expires: DateTime.Now.AddMinutes(jwtSetting.AccessExpiration),
                    signingCredentials: credentials
                )
            );
            //生成refreshToken
            var refreshToken = GenerateRefreshToken();

            //先判断是第一次得到token还是刷新token (老胡的方法是删除表中所有数据再加，这样来保证单个用户始终有唯一且最新数据)
            var appIdentityUser = _appIdentityUserRespository.Table.FirstOrDefault(
                x => x.UserId == appUser.Id
            );

            /*
            if (appIdentityUser == null)
            {
                //把这个token以及对应的refreshToken存进数据库
                appIdentityUser = new AppIdentityUser
                {
                    UserId = appUser.Id,
                    Username = appUser.Username,
                    RefreshToken = refreshToken,
                    RefreshTokenExpiration = DateTime.Now.AddMinutes(jwtSetting.RefreshExpiration)
                };
            }
            else
            {
                appIdentityUser.RefreshToken = refreshToken;
            }
        */
            appIdentityUser = new AppIdentityUser
            {
                UserId = appUser.Id,
                Username = appUser.Username,
                RefreshToken = refreshToken,
                RefreshTokenExpiration = DateTime.Now.AddMinutes(jwtSetting.RefreshExpiration)
            };
            var tempappIdentityUser = _appIdentityUserRespository.Table
                .Where(x => x.UserId == appUser.Id)
                .ToList();
            await _appIdentityUserRespository.DeleteBulkAsync(tempappIdentityUser, true);
            //最后再更新(更新既可以是新增也可以是更新,这点非常好)
            // await _appIdentityUserRespository.UpdateAsync(appIdentityUser);
            await _appIdentityUserRespository.AddAsync(appIdentityUser); //UpdateAsync的话会让创建时间默认为0000...，因为没走设置创建时间的，它的创建时间就是更改时间

            //返回token
            var appTokenDto = new AppTokenDto { AccessToken = token, RefreshToken = refreshToken };
            return appTokenDto;
        }

        //创建生成RefreshToken编码字符串
        private string GenerateRefreshToken()
        {
            var rndNumber = new byte[32];
            using var rnd = RandomNumberGenerator.Create();
            rnd.GetBytes(rndNumber);
            return Convert.ToBase64String(rndNumber);
        }

        //实现接口的刷新token方法
        public async Task<AppTokenDto> RefreshTokenAsync(AppTokenDto appTokenDto)
        {
            //获取生产token的配置
            var jwtSetting = _configuration.GetSection("JwtSetting").Get<JwtParameterModel>();

            //生成模拟验证token的参数对象
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(jwtSetting.Secret)
                ),
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = false
            };

            //开始模拟验证
            var tokenHandler = new JwtSecurityTokenHandler();

            //验证结果，返回token的详细信(out var securityToken)
            var principal = tokenHandler.ValidateToken(
                appTokenDto.AccessToken,
                tokenValidationParameters,
                out var securityToken
            );

            //拿着securityToken解析token信息开始判断生成token的用到的编码方式
            if (
                securityToken is not JwtSecurityToken jwtSecurityToken
                || !jwtSecurityToken.Header.Alg.Equals(
                    SecurityAlgorithms.HmacSha256,
                    StringComparison.InvariantCultureIgnoreCase
                )
            )
            {
                throw new SecurityTokenException("token编码方式匹配失败");
            }

            //解析验证结果principal进一步根据Claims的信息进行判断--解析出真正的名称
            var claimIdentity = principal.Identity as ClaimsIdentity;
            var username = claimIdentity!.FindFirst(x => x.Type == "Username")?.Value;
            var userid = claimIdentity!.FindFirst(x => x.Type == "UserId")?.Value;
            var realName = username == null ? "" : username;
            

            // 拿到principal解析token中获得Claims包含的username，去当前认证用户表查找对应记录
            // var appIdentityUser = _appIdentityUserRespository.Table.FirstOrDefault(
            //     x => x.Username.Equals(realName)
            // );
            var appIdentityUser = _appIdentityUserRespository.Table.FirstOrDefault(
                x => x.UserId==new Guid(userid)
            );

            if (
                appIdentityUser == null
                || appIdentityUser.RefreshToken != appTokenDto.RefreshToken
                || appIdentityUser.RefreshTokenExpiration <= DateTime.Now
            )
            {
                throw new Exception("传入的token或者refreshToken无效");
            }
            else
            {
                //验证成功，重新生成token和RefreshToken编码串
                var appUser = _appUserRespository.Table.FirstOrDefault(
                    x => x.Username.Equals(realName)
                );
                if (appUser != null)
                {
                    return await this.GenerateToken(appUser);
                }
                else
                {
                    return new AppTokenDto();
                }
            }
        }

        //实现接口 判断用户是否注册，已注册才能去生成token 的方法
        public async Task<AppTokenDto> ValidateUserAsync(CreateUserDto createUserDto)
        {
            // 使用传入的用户名和密码，验证用户名和密码是否都对，对则生成token，否则返回一个空token的对象
            var appUser = _appUserRespository.Table.FirstOrDefault(
                x =>
                    x.Username.Equals(createUserDto.Username)
                    && x.Password.Equals(createUserDto.Password)
            );

            if (appUser != null)
            {
                var appToken = await this.GenerateToken(appUser);
                return appToken;
                /*
                 //防止一个人token还没过期就再次进入导致刷新token和RefreshToken
                 var isHasTokenInAppIdentityUserTable =
                     _appIdentityUserRespository.Table.FirstOrDefault(
                         x => x.Username.Equals(createUserDto.Username)
                     );
                 if (isHasTokenInAppIdentityUserTable == null)
                 {
                     return await this.GenerateToken(appUser);
                 }
                 else
                 {
                     throw new Exception("该token仍然有效,无需更新");
                 }
                 */
            }
            else
            {
                return new AppTokenDto();
            }
        }
    }
}
